using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace WolfCarol.AvgToolkit.Tween
{
    public enum Ease
    {
        Linear = 0,
        InSine = 1,
        OutSine = 2,
        InOutSine = 3,
        InQuad = 4,
        OutQuad = 5,
        InOutQuad = 6,
        InCubic = 7,
        OutCubic = 8,
        InOutCubic = 9,
        InQuart = 10,
        OutQuart = 11,
        InOutQuart = 12,
        InQuint = 13,
        OutQuint = 14,
        InOutQuint = 15,
        InExpo = 16,
        OutExpo = 17,
        InOutExpo = 18,
        InCirc = 19,
        OutCirc = 20,
        InOutCirc = 21,
        InBack = 22,
        OutBack = 23,
        InOutBack = 24,
        InElastic = 25,
        OutElastic = 26,
        InOutElastic = 27,
        InBounce = 28,
        OutBounce = 29,
        InOutBounce = 30,
        InWaitOut = 31
    }

    public delegate float FloatToFloat(float t);

    public static class Interpolation
    {
        private static readonly FloatToFloat[] interpolations = new FloatToFloat[] {
        (float t) => t,
        (float t) => 1f - MathF.Cos(t * MathF.PI / 2f),
        (float t) => MathF.Sin(t * MathF.PI / 2f),
        (float t) => -(MathF.Cos(MathF.PI * t) - 1f) / 2f,
        (float t) => t * t,
        (float t) => 1f - (1f - t) * (1f - t),
        (float t) => t < 0.5f ? 2f * t * t : 1f - MathF.Pow(-2f * t + 2f, 2f) / 2f,
        (float t) => t * t * t,
        (float t) => 1f - MathF.Pow(1f - t, 3f),
        (float t) => t < 0.5f ? 4f * t * t * t : 1f - MathF.Pow(-2f * t + 2f, 3f) / 2f,
        (float t) => t * t * t * t,
        (float t) => 1f - MathF.Pow(1f - t, 4f),
        (float t) => t < 0.5f ? 8f * t * t * t * t : 1f - MathF.Pow(-2f * t + 2f, 4f) / 2f,
        (float t) => t * t * t * t * t,
        (float t) => 1f - MathF.Pow(1f - t, 5f),
        (float t) => t < 0.5f ? 16f * t * t * t * t * t : 1f - MathF.Pow(-2f * t + 2f, 5f) / 2f,
        (float t) => t == 0f ? 0f : MathF.Pow(2f, 10f * t - 10f),
        (float t) => t == 1f ? 1f : 1f - MathF.Pow(2f, -10f * t),
        (float t) => t == 0f ? 0f : t == 1f ? 1f: t < 0.5f ? MathF.Pow(2f, 20f * t - 10f) / 2f: (2f - MathF.Pow(2f, -20f * t + 10f)) / 2f,
        (float t) => 1 - MathF.Sqrt(1f - MathF.Pow(t, 2f)),
        (float t) => MathF.Sqrt(1f - MathF.Pow(t - 1f, 2f)),
        (float t) => t < 0.5f ? (1f - MathF.Sqrt(1f - MathF.Pow(2f * t, 2f))) / 2f : (MathF.Sqrt(1f - MathF.Pow(-2f * t + 2f, 2f)) + 1f) / 2f,
        (float t) => 2.70158f * t * t * t - 1.70158f * t * t,
        (float t) => 1f + 2.70158f * MathF.Pow(t - 1.70158f, 3f) + 1.70158f * MathF.Pow(t - 1f, 2f),
        (float t) => t < 0.5 ? MathF.Pow(2f * t, 2f) * (3.59491f * 2f * t - 2.59491f) / 2f : (MathF.Pow(2f * t - 2f, 2f) * (3.59491f * (t * 2f - 2f) + 2.59491f) + 2f) / 2f,
        (float t) => t == 0f ? 0f : t == 1f ? 1f : -MathF.Pow(2f, 10f * t - 10f) * MathF.Sin((t * 10f - 10.75f) * 2.0944f),
        (float t) => t == 0f ? 0f : t == 1f ? 1f : MathF.Pow(2f, -10f * t) * MathF.Sin((t * 10f - 0.75f) * 2.0944f) + 1f,
        (float t) => t == 0f ? 0f : t == 1f ? 1f : t < 0.5f ? -(MathF.Pow(2f, 20f * t - 10f) * MathF.Sin((20f * t - 11.125f) * 1.39626f)) / 2f : MathF.Pow(2f, -20f * t + 10f) * MathF.Sin((20f * t - 11.125f) * 1.39626f) / 2f + 1f,
        (float t) => 1f - interpolations[(int)Ease.OutBounce](1f - t),
        (float t) => {
            if (t < 1f / 2.75f) {
                return 7.5625f * t * t;
            } else if (t < 2f / 2.75f) {
                return 7.5625f * (t -= 1.5f / 2.75f) * t + 0.75f;
            } else if (t < 2.5f / 2.75f) {
                return 7.5625f * (t -= 2.25f / 2.75f) * t + 0.9375f;
            } else {
                return 7.5625f * (t -= 2.625f / 2.75f) * t + 0.984375f;
            }
        },
        (float t) => t < 0.5f ? (1f - interpolations[(int)Ease.OutBounce](1f - 2f * t)) / 2f : (1f + interpolations[(int)Ease.OutBounce](2f * t - 1f)) / 2f,
        (float t) => MathF.Min(-5f * t * t + 5f * t, 1f)
    };

        public static float Calc(float t, Ease ease)
        {
            return interpolations[(int)ease](t);
        }
    }

    public delegate T TweenGetter<T>();

    public delegate void TweenSetter<T>(T t);

    public delegate void TweenCallback();

    public abstract class TweenBase
    {
        public bool IsComplete { get; protected set; }

        public TweenCallback OnComplete { get; set; }

        public abstract void Next(float deltaTime);

        public abstract Task StartTween();
    }

    public abstract class Tween<T> : TweenBase
    {
        protected TweenGetter<T> getter;

        protected TweenSetter<T> setter;

        protected T startValue;

        protected T endValue;

        protected float timer;

        protected float duration;

        public Ease ease = Ease.Linear;

        public Tween(TweenGetter<T> _getter, TweenSetter<T> _setter, T _endValue, float _duration)
        {
            getter = _getter;
            setter = _setter;
            startValue = getter();
            endValue = _endValue;
            timer = 0f;
            duration = _duration;
            IsComplete = false;
        }

        public abstract void ChangeValue(float t);

        public override void Next(float deltaTime)
        {
            if (timer < duration)
            {
                float t = Interpolation.Calc(timer / duration, ease);
                ChangeValue(t);
                timer += deltaTime;
            }
            else
            {
                if (IsComplete == false)
                {
                    ChangeValue(1f);
                    OnComplete?.Invoke();
                    IsComplete = true;
                }
            }
        }

        public override async Task StartTween()
        {
            while (timer < duration)
            {
                float t = Interpolation.Calc(timer / duration, ease);
                ChangeValue(t);
                timer += Time.deltaTime;
                await Awaitable.NextFrameAsync();
            }
            ChangeValue(1f);
            OnComplete?.Invoke();
            IsComplete = true;
        }
    }

    public class IntTween : Tween<int>
    {
        public IntTween(TweenGetter<int> _getter, TweenSetter<int> _setter, int _endValue, float _duration) : base(_getter, _setter, _endValue, _duration) { }

        public override void ChangeValue(float t)
        {
            int v = Mathf.FloorToInt(Mathf.Lerp(startValue, endValue, t));
            setter(v);
        }
    }

    public class FloatTween : Tween<float>
    {
        public FloatTween(TweenGetter<float> _getter, TweenSetter<float> _setter, float _endValue, float _duration) : base(_getter, _setter, _endValue, _duration) { }

        public override void ChangeValue(float t)
        {
            float v = Mathf.Lerp(startValue, endValue, t);
            setter(v);
        }
    }

    public class Vector2Tween : Tween<Vector2>
    {
        public Vector2Tween(TweenGetter<Vector2> _getter, TweenSetter<Vector2> _setter, Vector2 _endValue, float _duration) : base(_getter, _setter, _endValue, _duration) { }

        public override void ChangeValue(float t)
        {
            Vector2 v = Vector2.Lerp(startValue, endValue, t);
            setter(v);
        }
    }

    public class Vector3Tween : Tween<Vector3>
    {
        public Vector3Tween(TweenGetter<Vector3> _getter, TweenSetter<Vector3> _setter, Vector3 _endValue, float _duration) : base(_getter, _setter, _endValue, _duration) { }

        public override void ChangeValue(float t)
        {
            Vector3 v = Vector3.Lerp(startValue, endValue, t);
            setter(v);
        }
    }

    public class ColorTween : Tween<Color>
    {
        public ColorTween(TweenGetter<Color> _getter, TweenSetter<Color> _setter, Color _endValue, float _duration) : base(_getter, _setter, _endValue, _duration) { }

        public override void ChangeValue(float t)
        {
            Color v = Color.Lerp(startValue, endValue, t);
            setter(v);
        }
    }

    public sealed class Sequence : TweenBase
    {
        private List<TweenBase> _tweenList;

        public Sequence()
        {
            IsComplete = false;
            _tweenList = new List<TweenBase>();
        }

        public void AddTween(TweenBase tween)
        {
            _tweenList.Add(tween);
        }

        public override void Next(float deltaTime)
        {
            for (int i = 0; i < _tweenList.Count; i++)
            {
                if (_tweenList[i].IsComplete)
                {
                    continue;
                }

                _tweenList[i].Next(deltaTime);
                return;
            }

            if (IsComplete == false)
            {
                OnComplete?.Invoke();
                IsComplete = true;
            }
        }

        public override async Task StartTween()
        {
            for (int i = 0; i < _tweenList.Count; i++)
            {
                await _tweenList[i].StartTween();
            }

            OnComplete?.Invoke();
            IsComplete = true;
        }
    }

    public static class TweenExtensions
    {
        public static FloatTween CreateVolumeTween(this AudioSource target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = new(() => target.volume, (x) => target.volume = x, endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoVolume(this AudioSource target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = target.CreateVolumeTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static ColorTween CreateColorTween(this SpriteRenderer target, Color endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            ColorTween tween = new(() => target.color, (x) => target.color = x, endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoColor(this SpriteRenderer target, Color endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            ColorTween tween = target.CreateColorTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static FloatTween CreateAlphaTween(this SpriteRenderer target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = new(() => target.color.a, (x) => target.color = new Color(target.color.r, target.color.g, target.color.b, x), endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoAlpha(this SpriteRenderer target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = target.CreateAlphaTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static ColorTween CreateColorTween(this Image target, Color endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            ColorTween tween = new(() => target.color, (x) => target.color = x, endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoColor(this Image target, Color endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            ColorTween tween = target.CreateColorTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static FloatTween CreateAlphaTween(this Image target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = new(() => target.color.a, (x) => target.color = new Color(target.color.r, target.color.g, target.color.b, x), endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoAlpha(this Image target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = target.CreateAlphaTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static FloatTween CreateAlphaTween(this TMP_Text target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = new(() => target.color.a, (x) => target.color = new Color(target.color.r, target.color.g, target.color.b, x), endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoAlpha(this TMP_Text target, float endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            FloatTween tween = target.CreateAlphaTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static Vector3Tween CreateMoveTween(this Transform target, Vector3 endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            Vector3Tween tween = new(() => target.position, (x) => target.position = x, endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoMove(this Transform target, Vector3 endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            Vector3Tween tween = target.CreateMoveTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static Vector2Tween CreateMoveTween(this RectTransform target, Vector2 endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            Vector2Tween tween = new(() => target.anchoredPosition, (x) => target.anchoredPosition = x, endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoMove(this RectTransform target, Vector2 endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            Vector2Tween tween = target.CreateMoveTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static Vector3Tween CreateScaleTween(this RectTransform target, Vector3 endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            Vector3Tween tween = new(() => target.localScale, (x) => target.localScale = x, endValue, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoScale(this RectTransform target, Vector3 endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            Vector3Tween tween = target.CreateScaleTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }

        public static IntTween CreateTextTween(this TMP_Text target, string endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            target.ForceMeshUpdate();
            target.maxVisibleCharacters = target.textInfo.characterCount;
            target.text += endValue;
            target.ForceMeshUpdate();
            int length = target.textInfo.characterCount;
            IntTween tween = new(() => target.maxVisibleCharacters, (x) => target.maxVisibleCharacters = x, length, duration)
            {
                ease = ease,
                OnComplete = callback
            };
            return tween;
        }

        public static async Task DoText(this TMP_Text target, string endValue, float duration, Ease ease = Ease.Linear, TweenCallback callback = null)
        {
            IntTween tween = target.CreateTextTween(endValue, duration, ease, callback);
            await tween.StartTween();
        }
    }
}